////////////////////////////////////////////////////////////////////////////////
// Includes
////////////////////////////////////////////////////////////////////////////////

#include "SCVMCommand.hpp"
#include "SCProgram.hpp"
#include "SCEngine.hpp"
#include "SComputationInfo.hpp"
#include "InterruptionException.hpp"
#include "IOFileException.hpp"
#include <exception>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <fstream>
//#include <unistd.h>
#include <signal.h>

////////////////////////////////////////////////////////////////////////////////
// Namespaces
////////////////////////////////////////////////////////////////////////////////

namespace SC {

////////////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////////////

using namespace std;
using namespace Exceptions;

// Loop condition
static bool run = true;

// Message
static string end_msg[2];

// Ctrl+C signal handler
static void sigfun(int sig) {
	run = false;
	end_msg[0] = "user";
	end_msg[1] = "";
}

const bool SCVMCommand::Command(const vector<string> & arguments) {
	// Input / Output
	string input;
	// Copy arguments
	vector<string> args(arguments), plugins_dirs;
	// Remove command name
	args.erase(args.begin());
	// Recording file (none by default)
	string recording_filename;
	// Arguments processing
	unsigned int to_del;
	while (args.size() > 0) {
		to_del = 1;
		// If help is required
		if ((args[0] == "-h") || (args[0] == "--help")) {
			this->help(arguments[0]);
			return true;
		}
		// Plugin directory
		if (((args[0] == "-p") || (args[0] == "--plug_dir")) && (args.size() > 1)) {
			string dirs = args[1];
			if (dirs.empty()) {
				this->error();
				this->help(arguments[0]);
				return false;
			}
			while (dirs.size() > 0) {
				string::size_type idx = dirs.find_first_of(';');
				if (idx == string::npos) idx = dirs.size();
				plugins_dirs.push_back(dirs.substr(0,idx));
				dirs.erase(0,idx);
				if (dirs.size() > 0) dirs.erase(0,1);
			}
			to_del = 2;
		}
		// Recording mode
		else if (((args[0] == "-r") || (args[0] == "--record")) && (args.size() > 1)) {
			recording_filename = args[1];
			to_del = 2;
		}
		// Wrong option
		else if ((args[0].substr(0,1) == "-") || (args[0].substr(0,2) == "--")) {
			this->error("incorrect option or incorrect usage of "+args[0]);
			this->help(arguments[0]);
			return false;
		}
		// Input
		else if (input.empty())
			input = args[0];
		else {
			this->error();
			this->help(arguments[0]);
			return false;
		}
		while (to_del > 0) {
			args.erase(args.begin());
			--to_del;
		}
	}
	// If no input, then error
	if (input.empty()) {
		this->error();
		this->help(arguments[0]);
		return false;
	}
	// Input formatting
	if ((input.size() > 4) && (input.substr(input.size()-4,4) != ".bsc"))
		input += ".bsc";
	// Run VM
	try {
		// Read program
		SCProgram program;
		program.ReadByteCode(input);
		// Setup engine with data
		SCEngine engine;
		if (plugins_dirs.empty()) plugins_dirs.push_back(".");
		engine.InitData(program,plugins_dirs);
		// Setup recording
		bool recording = false;
		ofstream recording_file;
		if (!recording_filename.empty()) {
			recording_file.open(recording_filename.c_str());
			if (!recording_file.is_open())
				throw IOFileException("Cannot open recording file: "+recording_filename);
			recording = true;
			engine.SetRecordingLevel(2);
		}
		// <Ctrl+C> Signal handler
		signal(SIGINT,sigfun);
		// Display message
		this->ostream << "SC Virtual Machine running : press <Ctrl+C> to end computation" << endl;
		// Launch loop
		unsigned long final_time, init_time = time(0);
		while (run) {
			try {
				bool computed = false;
				try { computed = engine.Iterate(); }
				catch (InterruptionException & ie) {
					end_msg[0] = "interruption";
					end_msg[1] = ie.Comment();
					run = false;
				}
				catch (exception & e ) { cerr << e.what() << endl; }
				if (recording) engine.WriteLastComputation(recording_file);
			}
			catch (exception & e) {
				if (recording_file.is_open()) recording_file.close();
				throw Exception(e.what());
			}
		}
		final_time = time(0);
		if (recording_file.is_open()) recording_file.close();
		this->ostream << "SC Virtual Machine stopped on " << end_msg[0] << " request" << (end_msg[1].empty()?"":" : " + end_msg[1]) << endl;
    	this->ostream << "\tNumber of iterations     :   " << engine.GetNbIterations() << endl;
    	this->ostream << "\tNumber of computations   :   " << engine.GetNbComputations() << endl;
    	this->ostream << "\tTotal running time (s)   :   " << (final_time-init_time) << endl;
	}
	catch(exception & e) {
		this->ostream << "SC Virtual Machine stopped by Fatal Exception" << endl;
		this->ostream << e.what() << endl;
		return false;
	}
	return true;
}
		
void SCVMCommand::help(const string & name) {
	this->ostream << "Usage: " + name + " [options] input" << endl;
	this->ostream << "Arguments:" << endl;
	this->ostream << "\t input                  : Bytecode input \".bsc\" file" << endl;
	this->ostream << "Options:" << endl;
	this->ostream << "\t -p | --plug_dir <dirs> : Plugins directories seprated by ';'" << endl;
	this->ostream << "\t -r | --record   <file> : Records execution into the given file" << endl;
	this->ostream << "\t -h | --help            : Displays help information" << endl;
}
		
void SCVMCommand::error(const string & msg) {
	this->ostream << "Incorrect command use" << (!msg.empty()?": "+msg:"") << endl;
}

////////////////////////////////////////////////////////////////////////////////
// Closing Namespaces
////////////////////////////////////////////////////////////////////////////////

}

////////////////////////////////////////////////////////////////////////////////
